package org.wheel.base.utils;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

/**
 * 表达式解析器
 *
 * @author klaus
 * @date 2020/9/4 华小勇 11:39
 */
public class ExpressionUtils {

    static final Logger logger = LoggerFactory.getLogger(ExpressionUtils.class);

    static ExpressionParser parser = new SpelExpressionParser();

    static Map<String, Expression> cache = new ConcurrentHashMap<>();

    static MapAccessor accessor = new MapAccessor() {

        @Override
        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
            if (target != null && target instanceof Map) {
                return new TypedValue(((Map) target).getOrDefault(name, null));
            }
            return super.read(context, target, name);
        }

        @Override
        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
            if (target != null && target instanceof Map) {
                return true;
            }
            return super.canRead(context, target, name);
        }
    };

    static ThreadLocal<Object> threadLocal = new ThreadLocal<>();

    static StandardEvaluationContext context = new StandardEvaluationContext() {
        @Override
        public TypedValue getRootObject() {
            return Optional.ofNullable(threadLocal.get()).map((e) -> new TypedValue(e)).orElse(TypedValue.NULL);
        }
    };

    static {
        Stream.of(ReflectionUtils.getDeclaredMethods(ExpFunction.class))
                .forEach(m -> registerFunction(m.getName(), m));
        context.addPropertyAccessor(accessor);
    }

    public static void registerFunction(String name, Method method) {
        context.registerFunction(name, method);
    }

    static Expression cacheExp(String exp) {
        return cache.computeIfAbsent(exp, (k) -> {
            try {
                return parser.parseExpression(k);
            } catch (ParseException e) {
                logger.error(e.getMessage(), e);
            }
            return null;
        });
    }

    public static <T> T getValue(String exp, Object root, Class<T> type) {
        try {
            threadLocal.set(root);
            T ret = cacheExp(exp).getValue(context, type);
            return ret;
        } catch (EvaluationException e) {
            logger.error(e.getMessage(), e);
        } finally {
            threadLocal.remove();
        }
        return null;
    }

    public static Object getValue(String exp, Object root) {
        return getValue(exp, root, null);
    }

    public static class ExpFunction {

        static Map<String, AtomicInteger> atomicIntegerMap = new ConcurrentHashMap<>();

        public static Object seq(String pattern) {

            return atomicIntegerMap.computeIfAbsent(pattern, (e) -> new AtomicInteger(1)).getAndAdd(1);
        }

        public static String numberFormat(Object value, String pattern) {

            return String.format(String.format("%s0%sd", "%", pattern), value);
        }

        public static Date sysDate() {

            return new Date();
        }

        public static String dateFormat(Date date, String pattern) {

            return DateFormatUtils.format(date, pattern);
        }

        public static Date date(Integer date) {

            return addDays(new Date(), date);
        }

        public static Date addDays(Date date, Integer n) {

            return DateUtils.addDays(date, Optional.ofNullable(n).orElse(0));
        }
    }

    public static void main(String[] args) throws Exception {

        Map<String, Object> params = new HashMap<>(16);
        params.put("id", 45);
        params.put("isUpdate", false);
        params.put("page", 2);
        params.put("name", "学生");
        params.put("sysDate", new Date());
        params.put("nameLike", "'%' + name + '%'");

        Object value = getValue("isUpdate ? (b = 'update') : (b = 'insert')", params);

        System.out.println(value);

        value = getValue("'select * from user where name like ' + '%' + name + '%'", params);

        System.out.println(value);
    }
}
